M2.879 · TFM · Área 2 · EDA
2022-1 · Máster universitario en Ciencia de datos (Data science)
Estudios de Informática, Multimedia y Telecomunicación
zerowaste y ResortIt)¶En este notebook se realiza un análisis exploratorio inicial (EDA) del conjunto de datos zerowaste y ResortIt. Estos datasets contienen imágenes de papel y plástico en el contexto deseado (un conveyor belt). Las imágenes pueden contener uno o varios objetos. En el caso de ResortIt, los objetos son sintéticos.
from pathlib import Path
import os
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
import seaborn as sns
import json
from waste_detection_system import shared_data as base, utils
# plot style
# ==============================================================================
plt.rcParams['axes.titlesize'] = 12
plt.rcParams['figure.titlesize'] = 16
utils.clean_datasets()
zerowaste-f)¶Se trata de un conjunto de datos en el contexto ideal (imágenes capturadas con una cámara encima de una cinta donde van pasando los residuos), con una o varias anotaciones por imagen. Las categorías reflejadas en este conjunto de datos son:
Las anotaciones se encuentran en formato COCO.
jsons = {}
for dirpath, dirs, filenames in os.walk(base.ZERO_WASTE):
full_path = [os.sep.join([dirpath, filename]) for filename in filenames
if filename.endswith('.json')]
for _path in full_path:
with open(_path, 'r') as _file:
jsons[dirpath] = json.load(_file)
partitions = {'test', 'train', 'val'}
images = {
'name' : [],
'path' : [],
'width' : [],
'height' : [],
'type' : [],
'label' : [],
'bbox-x' : [],
'bbox-y' : [],
'bbox-w' : [],
'bbox-h' : [],
}
for _path, json_file in jsons.items():
partition_type = [part for part in partitions if part in _path][0]
for image in json_file['images']:
anns = [item for item in json_file['annotations']
if item['image_id'] == image['id']]
for ann in anns:
cat = [item for item in json_file['categories']
if item['id'] == ann['category_id']][0]
images['name'] = images['name'] + [image['file_name']]
images['path'] = images['path'] + [str(Path(_path) / 'data' / image['file_name'])]
images['width'] = images['width'] + [image['width']]
images['height'] = images['height'] + [image['height']]
images['type'] = images['type'] + [partition_type]
images['label'] = images['label'] + [cat['name']]
bbox = ann['bbox']
images['bbox-x'] = images['bbox-x'] + [bbox[0]]
images['bbox-y'] = images['bbox-y'] + [bbox[1]]
images['bbox-w'] = images['bbox-w'] + [bbox[2]]
images['bbox-h'] = images['bbox-h'] + [bbox[3]]
images_df = pd.DataFrame(images)
images_df['label'] = [base.RELATION_CATS[label.upper()] for label in images_df['label']]
images_df.head(n=10)
| name | path | width | height | type | label | bbox-x | bbox-y | bbox-w | bbox-h | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 05_frame_000001.PNG | raw-datasets\zero-waste\zerowaste-f\test\data\... | 1920 | 1080 | test | PLASTICO | 1737.3 | 26.3 | 182.7 | 153.1 |
| 1 | 05_frame_000001.PNG | raw-datasets\zero-waste\zerowaste-f\test\data\... | 1920 | 1080 | test | PLASTICO | 1024.7 | 399.4 | 311.7 | 443.3 |
| 2 | 05_frame_000001.PNG | raw-datasets\zero-waste\zerowaste-f\test\data\... | 1920 | 1080 | test | PLASTICO | 554.2 | 2.5 | 525.4 | 492.9 |
| 3 | 05_frame_000001.PNG | raw-datasets\zero-waste\zerowaste-f\test\data\... | 1920 | 1080 | test | METAL | 1416.6 | 408.2 | 192.2 | 141.3 |
| 4 | 05_frame_000001.PNG | raw-datasets\zero-waste\zerowaste-f\test\data\... | 1920 | 1080 | test | PLASTICO | 1120.0 | 208.6 | 310.7 | 253.2 |
| 5 | 05_frame_000001.PNG | raw-datasets\zero-waste\zerowaste-f\test\data\... | 1920 | 1080 | test | PAPEL | 597.5 | 861.2 | 223.9 | 123.4 |
| 6 | 05_frame_000001.PNG | raw-datasets\zero-waste\zerowaste-f\test\data\... | 1920 | 1080 | test | PAPEL | 189.2 | 228.4 | 374.7 | 314.2 |
| 7 | 05_frame_000011.PNG | raw-datasets\zero-waste\zerowaste-f\test\data\... | 1920 | 1080 | test | PLASTICO | 1864.5 | 20.4 | 55.5 | 147.6 |
| 8 | 05_frame_000011.PNG | raw-datasets\zero-waste\zerowaste-f\test\data\... | 1920 | 1080 | test | PLASTICO | 1167.3 | 402.6 | 296.4 | 439.8 |
| 9 | 05_frame_000011.PNG | raw-datasets\zero-waste\zerowaste-f\test\data\... | 1920 | 1080 | test | PLASTICO | 629.5 | 1.3 | 594.2 | 492.1 |
len(images_df.index)
26766
images_df.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 26766 entries, 0 to 26765 Data columns (total 10 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 name 26766 non-null object 1 path 26766 non-null object 2 width 26766 non-null int64 3 height 26766 non-null int64 4 type 26766 non-null object 5 label 26766 non-null object 6 bbox-x 26766 non-null float64 7 bbox-y 26766 non-null float64 8 bbox-w 26766 non-null float64 9 bbox-h 26766 non-null float64 dtypes: float64(4), int64(2), object(4) memory usage: 2.0+ MB
images_df['type'].value_counts()
train 18002 test 5077 val 3687 Name: type, dtype: int64
images_df['type'].value_counts(normalize=True)
train 0.672570 test 0.189681 val 0.137749 Name: type, dtype: float64
images_df['label'].value_counts(normalize=True)
PAPEL 0.663192 PLASTICO 0.322536 METAL 0.014272 Name: label, dtype: float64
images_df['label'].value_counts(normalize=False)
PAPEL 17751 PLASTICO 8633 METAL 382 Name: label, dtype: int64
images_df['width'].value_counts(normalize=True)
1920 1.0 Name: width, dtype: float64
images_df['height'].value_counts(normalize=True)
1080 1.0 Name: height, dtype: float64
sample_imgs = images_df[(images_df.type == 'train')].sample(n=3)
utils.plot_data_sample(sample_imgs, images_df)
with open(base.ZERO_WASTE_CSV, 'w', encoding='utf-8-sig') as f:
images_df.to_csv(f, index=False)
images_df['path'].map(lambda p: Path(p).suffix).value_counts()
.PNG 26766 Name: path, dtype: int64
Se trata de un conjunto de datos artificial, creado a partir de recortes de residuos de plástico tomados en vista aérea superpuestos sobre diferentes fondos, con una o varias anotaciones por imagen. Las categorías reflejadas en este conjunto de datos son:
Las anotaciones se encuentran en formato COCO.
jsons = {}
for dirpath, dirs, filenames in os.walk(base.RESORTIT):
full_path = [os.sep.join([dirpath, filename]) for filename in filenames
if filename.endswith('.json')]
for _path in full_path:
with open(_path, 'r') as _file:
jsons[_path] = json.load(_file)
partitions = {'test', 'train', 'val'}
images = {
'name' : [],
'path' : [],
'width' : [],
'height' : [],
'type' : [],
'label' : [],
'bbox-x' : [],
'bbox-y' : [],
'bbox-w' : [],
'bbox-h' : [],
}
for _path, json_file in jsons.items():
partition_type = [part for part in partitions if part in _path][0]
for image in json_file['images']:
anns = [item for item in json_file['annotations']
if item['image_id'] == image['id']]
for ann in anns:
cat = [item for item in json_file['categories']
if item['id'] == ann['category_id']][0]
images['name'] = images['name'] + [image['file_name']]
images['path'] = images['path'] + [str(Path(_path).parents[1] / partition_type / image['file_name'])]
images['width'] = images['width'] + [image['width']]
images['height'] = images['height'] + [image['height']]
images['type'] = images['type'] + [partition_type]
images['label'] = images['label'] + [cat['name']]
bbox = ann['bbox']
images['bbox-x'] = images['bbox-x'] + [bbox[0]]
images['bbox-y'] = images['bbox-y'] + [bbox[1]]
images['bbox-w'] = images['bbox-w'] + [bbox[2]]
images['bbox-h'] = images['bbox-h'] + [bbox[3]]
images_df = pd.DataFrame(images)
images_df['label'] = [base.RELATION_CATS[label.upper()] for label in images_df['label']]
images_df.head(n=10)
| name | path | width | height | type | label | bbox-x | bbox-y | bbox-w | bbox-h | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 20000.jpg | raw-datasets\ResortIt\train\20000.jpg | 800 | 800 | train | METAL | 330 | 337 | 136 | 172 |
| 1 | 20000.jpg | raw-datasets\ResortIt\train\20000.jpg | 800 | 800 | train | METAL | 256 | 278 | 126 | 172 |
| 2 | 19999.jpg | raw-datasets\ResortIt\train\19999.jpg | 800 | 800 | train | METAL | 376 | 376 | 128 | 177 |
| 3 | 19999.jpg | raw-datasets\ResortIt\train\19999.jpg | 800 | 800 | train | METAL | 307 | 294 | 137 | 169 |
| 4 | 19998.jpg | raw-datasets\ResortIt\train\19998.jpg | 800 | 800 | train | METAL | 226 | 341 | 49 | 50 |
| 5 | 19998.jpg | raw-datasets\ResortIt\train\19998.jpg | 800 | 800 | train | METAL | 240 | 360 | 119 | 134 |
| 6 | 19997.jpg | raw-datasets\ResortIt\train\19997.jpg | 800 | 800 | train | METAL | 307 | 278 | 132 | 155 |
| 7 | 19997.jpg | raw-datasets\ResortIt\train\19997.jpg | 800 | 800 | train | METAL | 362 | 138 | 162 | 162 |
| 8 | 19996.jpg | raw-datasets\ResortIt\train\19996.jpg | 800 | 800 | train | METAL | 388 | 127 | 172 | 104 |
| 9 | 19996.jpg | raw-datasets\ResortIt\train\19996.jpg | 800 | 800 | train | METAL | 187 | 281 | 21 | 56 |
len(images_df.index)
43200
images_df.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 43200 entries, 0 to 43199 Data columns (total 10 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 name 43200 non-null object 1 path 43200 non-null object 2 width 43200 non-null int64 3 height 43200 non-null int64 4 type 43200 non-null object 5 label 43200 non-null object 6 bbox-x 43200 non-null int64 7 bbox-y 43200 non-null int64 8 bbox-w 43200 non-null int64 9 bbox-h 43200 non-null int64 dtypes: int64(6), object(4) memory usage: 3.3+ MB
images_df['type'].value_counts()
train 32000 val 11200 Name: type, dtype: int64
images_df['type'].value_counts(normalize=True)
train 0.740741 val 0.259259 Name: type, dtype: float64
images_df['label'].value_counts(normalize=True)
PLASTICO 0.50 METAL 0.25 PAPEL 0.25 Name: label, dtype: float64
images_df['label'].value_counts(normalize=False)
PLASTICO 21600 METAL 10800 PAPEL 10800 Name: label, dtype: int64
images_df['width'].value_counts(normalize=True)
800 1.0 Name: width, dtype: float64
images_df['height'].value_counts(normalize=True)
800 1.0 Name: height, dtype: float64
sample_imgs = images_df[(images_df.type == 'train')].sample(n=3)
utils.plot_data_sample(sample_imgs, images_df)
with open(base.RESORTIT_CSV, 'w', encoding='utf-8-sig') as f:
images_df.to_csv(f, index=False)
images_df['path'].map(lambda p: Path(p).suffix).value_counts()
.jpg 43200 Name: path, dtype: int64
with open(base.FINAL_DATA_CSV, 'w', encoding='utf-8-sig') as final_file,\
open(base.ZERO_WASTE_CSV, 'r', encoding='utf-8-sig') as zerowaste_file,\
open(base.RESORTIT_CSV, 'r', encoding='utf-8-sig') as resortit_file:
zerowaste = pd.read_csv(zerowaste_file)
zerowaste['dataset'] = 'final'
zerowaste = zerowaste.apply(lambda i : utils.batch_conversion_to_jpg(i, resize=False,
dataset_type=utils.DATASET_TYPES.FINAL), axis=1)
resortit = pd.read_csv(resortit_file)
resortit['dataset'] = 'complementary'
resortit = resortit.apply(lambda i : utils.batch_conversion_to_jpg(i, resize=False,
dataset_type=utils.DATASET_TYPES.COMPLEMENTARY), axis=1)
images_df = pd.concat([zerowaste, resortit])
images_df.to_csv(final_file, index=False)
fig, axes = plt.subplots(ncols=2, nrows=2, figsize=(15,10), dpi=300, sharey=True)
axes = axes.flatten()
fig.suptitle('Estadísticas del dataset', fontsize=20)
sns.set_theme()
zw_label_plot = axes[0]
ri_label_plot = axes[1]
final_label_plot = axes[2]
type_plot = axes[3]
zw_filter = images_df['dataset']=='final'
zw = images_df[zw_filter]
zw_label_info = zw['label'].value_counts(normalize=True)
zw_label_plot = sns.barplot(ax=zw_label_plot, data=zw_label_info.reset_index(), x='index', y='label', palette='rocket',
hue='label', dodge=False)
for container in zw_label_plot.containers:
zw_label_plot.bar_label(container, fmt='%.2f')
zw_label_plot.get_legend().remove()
zw_label_plot.set(xlabel='etiqueta', ylabel='', ylim=(0.0, 1.0))
zw_label_plot.set_title('ZeroWaste')
ri_filter = images_df['dataset']=='complementary'
ri = images_df[ri_filter]
ri_label_info = ri['label'].value_counts(normalize=True)
ri_label_plot = sns.barplot(ax=ri_label_plot, data=ri_label_info.reset_index(), x='index', y='label', palette='rocket',
hue='label', dodge=False)
for container in ri_label_plot.containers:
ri_label_plot.bar_label(container, fmt='%.2f')
ri_label_plot.get_legend().remove()
ri_label_plot.set(xlabel='etiqueta', ylabel='', ylim=(0.0, 1.0))
ri_label_plot.set_title('ResortIt')
label_info = images_df['label'].value_counts(normalize=True)
type_info = images_df['type'].value_counts(normalize=True)
final_label_plot = sns.barplot(ax=final_label_plot, data=label_info.reset_index(), x='index', y='label', palette='rocket',
hue='label', dodge=False)
for container in final_label_plot.containers:
final_label_plot.bar_label(container, fmt='%.2f')
final_label_plot.get_legend().remove()
final_label_plot.set(xlabel='etiqueta', ylabel='', ylim=(0.0, 1.0))
final_label_plot.set_title('Distribución conjunta de clases')
type_plot = sns.barplot(ax=type_plot, data=type_info.reset_index(), x='index', y='type', palette='rocket',
hue='type', dodge=False)
for container in type_plot.containers:
type_plot.bar_label(container, fmt='%.2f')
type_plot.get_legend().remove()
type_plot.set(xlabel='tipo', ylabel='', ylim=(0.0, 1.0))
type_plot.set_title('Distribución en train/val/test')
fig.tight_layout()
plt.show()
Se observa la distribución de observaciones por etiqueta:
| PAPEL | PLÁSTICO | METAL | VIDRIO | ORGÁNICO | OTROS | TOTAL | |
|---|---|---|---|---|---|---|---|
| % Zero Waste | 63.32% | 32.25% | 1.43% | 0.0% | 0.0% | 0.0% | 100% |
| % ResortIt | 25% | 50% | 25% | 0.0% | 0.0% | 0.0% | 100% |
| Nº observaciones ZW | 17.751 | 8.633 | 382 | 0 | 0 | 0 | 26.766 |
| Nº observaciones RI | 10.800 | 21.600 | 10.800 | 0 | 0 | 0 | 43.200 |
El conjunto de datos ZeroWaste no está balanceado: papel y plástico ocupan más del 95% del conjunto de datos, mientras que solo hay 382 observaciones de metal y no están disponibles anotaciones de vidrio, residuos orgánicos y otros residuos no reciclables. Respecto a ResortIt, muestra una distribución algo menos desbalanceada que ZeroWaste, con el doble de plástico que de papel y metal. Tampoco contiene vidrio, orgánico u otros residuos no reciclables.
Este dataset no representa la distribución de datos real que se va a encontrar en el ámbito de aplicación: recuperación de residuos reciclables de la fracción resto, debido a las siguientes razones:
La unión de estos cuatro conjuntos de datos produce la siguiente distirbución de clases:
| PAPEL | PLASTICO | VIDRIO | METAL | ORGANICO | OTROS | TOTAL | |
|---|---|---|---|---|---|---|---|
| % | 47.55% | 39.02% | 3.53% | 0.99% | 0.02% | 8.87% | 100% |
| Nº observaciones | 18.338 | 15.049 | 1.362 | 382 | 8 | 3.422 | 38.561 |
images_df = images_df[images_df.label.isin([base.CATS_PAPEL, base.CATS_PLASTICO])]
images_df['label'].value_counts()
PLASTICO 30233 PAPEL 28551 Name: label, dtype: int64
with open(base.FINAL_DATA_CSV, 'w', encoding='utf-8-sig') as f:
images_df.to_csv(f, index=False)
fig, axes = plt.subplots(ncols=2, nrows=1, figsize=(15,5), dpi=300, sharey=True)
fig.suptitle('Estadísticas del dataset', fontsize=20)
sns.set_theme()
label_plot = axes[0]
type_plot = axes[1]
label_info = images_df['label'].value_counts(normalize=True)
type_info = images_df['type'].value_counts(normalize=True)
label_plot = sns.barplot(ax=label_plot, data=label_info.reset_index(), x='index', y='label', palette='rocket',
hue='label', dodge=False)
for container in label_plot.containers:
label_plot.bar_label(container, fmt='%.2f')
label_plot.get_legend().remove()
label_plot.set(xlabel='etiqueta', ylabel='', ylim=(0.0, 1.0))
type_plot = sns.barplot(ax=type_plot, data=type_info.reset_index(), x='index', y='type', palette='rocket',
hue='type', dodge=False)
for container in type_plot.containers:
type_plot.bar_label(container, fmt='%.2f')
type_plot.get_legend().remove()
type_plot.set(xlabel='tipo', ylabel='', ylim=(0.0, 1.0))
fig.tight_layout()
plt.show()
La distribución final de las clases es la siguiente:
| PAPEL | PLÁSTICO | TOTAL | |
|---|---|---|---|
| % | 51% | 49% | 100% |
| Nº observaciones | 30.233 | 28.551 | 58.784 |
Finalmente se ha decidido descartar la creación de un conjunto de datos completo y balanceado debido a que los nuevos datos estaban fuera de contexto (Garbage In, Garbage Out) y no solucionaban el problema de desbalanceo de clases. Así mismo, se ha decidido acotar el ámbito del estudio a la detección y clasificación de papel y plástico (se descarta la categoría metal por no tener una muestra significativa en el dataset final ZeroWaste).
La justificación detrás de esta decisión recae en los medios utilizados actualmente para la segregación de residuos, que se basan en combinaciones de fuerzas mecánicas y magnéticas, entre otras. De esta forma, el papel y el plástico ligero (envoltorios) son separados de materiales más pesados como pueden ser el vidrio, plástico duro y material orgánico.
Debido a que el papel y el plástico ligero comparten características, su segregación mediante fuerzas mecánicas es más complicada. El objetivo de este proyecto consiste en la creación de un sistema de detección de papel y plástico a través de una cámara montada encima de una cinta transportadora de residuos. Futuras líneas de investigación incluirían la creación de un sistema robótico de segregación o la creación de un dataset más completo que permitiera realizar un trabajo más a fondo en la separación de residuos reciclables de la fracción resto.